#pragma once

#include<iostream>
#include<cerrno>
#include<cstring>
#include<cstdlib>
#include<functional>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unordered_map>
#include"RingQueue.hpp"
#include"lockGuard.hpp"
#include<pthread.h>
#include"Thread.hpp"
#include"err.hpp"

namespace ns_server
{
    const static uint16_t default_port = 0000;//默认端口号
    using func_t = std::function<std::string(std::string)>;
    class UdpServer
    {
    public:
        UdpServer(uint16_t port = default_port):port_(port)
        {
            std::cout<<"server addr: "<<port_<<std::endl;
            pthread_mutex_init(&lock,nullptr);

            //生产者收客户端消息，并将这些消息放到环形队列中，然后发送给客户端

            //将类内方法直接绑定给底层对应线程所需要的回调
            p = new Thread(1,std::bind(&UdpServer::Recv,this));
            c = new Thread(2,std::bind(&UdpServer::Broadcast,this));
        }
        void start()
        {
            //domain: AF_INET网络通信,AF_UNIX本地通信
            //type: SOCK_DGRAM基于UDP的网络通信，用户数据报服务
            //SOCK_STREAM基于TCP的网络通信，流式套接字
            //protocol: 创建套接字的协议类别,设置为0，系统会根据前两个参数自动推导

            //a.创建socket接口，打开网络文件，返回文件描述符
            //注意只是在OS层面上打开了一个文件，还没有与网络关联起来
            //int socket(int domain,int type,int protocol)
            sock_ = socket(AF_INET,SOCK_DGRAM,0);
            if(sock_<0)
            {
                std::cerr<<"create socket error"<<strerror(errno)<<std::endl;
                exit(SOCKET_ERR);
            }
            //返回的文件描述符应该是3
            std::cout<<"create socket success: "<<sock_<<std::endl;

            //b.给服务器指明IP地址和端口号port
            //int bind(int sockfd,const struct sockaddr* addr,socklen_t addrlen);
            //addr：网络相关的属性信息，包括协议家族、IP地址、端口号等。
            struct sockaddr_in local;
            bzero(&local,sizeof(local));
            local.sin_family = AF_INET;//PF_INET,网络间通信  
            local.sin_port=htons(port_);//主机字节序与网络字节序要进行转换,网络 默认大端字节序
            //local.sin_addr.s_addr = inet_addr(ip_.c_str());//字符串ip转换成整数ip的函数inet_addr
            //云服务器或者一款服务器，不用指明ip地址
            local.sin_addr.s_addr = INADDR_ANY;
            if(bind(sock_,(struct sockaddr*)&local,sizeof(local))<0)
            {
                std::cerr<<"bind socket error: "<<strerror(errno)<<std::endl;
                exit(BIND_ERR);
            }
            std::cout<<"bind socket success: "<<sock_<<std::endl;

            p->run();
            c->run();
        }

        /*void Start()
        {
            char buffer[1024];//自定义缓冲区
            while(true)
            {
                //ssize_t recvfrom(int sockfd, void *buf, size_t len, 
                //          int flags, struct sockaddr *src_addr, socklen_t *addrlen);
                //sockfd：对应操作的文件描述符。表示从该文件描述符索引的文件当中读取数据。
                //src_addr：对端网络相关的属性信息，包括协议家族、IP地址、端口号等。
                //addrlen：调用时传入期望读取的src_addr结构体的长度，返回时代表实际读取到的src_addr结构体的长度，这是一个输入输出型参数。
                //0表示阻塞读取

                //1.【收.读】：将客户端的发送的消息读到缓冲区中，
                //将用户端的属性信息设置进struct sockaddr结构体中
                struct sockaddr_in peer;
                socklen_t len = sizeof(peer);
                int n = recvfrom(sock_,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);
                if(n>0) buffer[n] = '\0';
                else continue;

                //提取client信息---debug
                //将整数IP转换成字符串IP的函数叫做inet_ntoa
                std::string clientip = inet_ntoa(peer.sin_addr);
                uint16_t clientport = ntohs((peer.sin_port));
                //打印获取到的客户端ip地址和端口号，以及内容
                std::cout<<clientip<<"-"<<clientport<<"# "<<buffer<<std::endl;

                //buffer是服务器收到的客户端消息，交给service_函数做业务处理
                //回调service函数，返回类型是个字符串,参数也是字符串
                std::string massage;
                //【发】:把缓冲区中的有效字符发给客户端,不带'\0',strlen无需+1
                //ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                //      const struct sockaddr *dest_addr, socklen_t addrlen);
                //服务器将客户端的需求执行完后，返回给客户端
                sendto(sock_,massage.c_str(),massage.size(),0,(struct sockaddr*)&peer,sizeof(peer));
            }
        }*/
        void addUser(const std::string& name,const struct sockaddr_in& peer)
        {
            LockGuard lockguard(&lock);
            auto iter = onlineuser.find(name);
            if(iter!=onlineuser.end()) return;
            onlineuser.insert(std::pair<const std::string,const struct sockaddr_in>(name,peer));
        }
        void Recv()
        {
            char buffer[1024];//自定义缓冲区
            while(true)
            {
                //ssize_t recvfrom(int sockfd, void *buf, size_t len, 
                //          int flags, struct sockaddr *src_addr, socklen_t *addrlen);
                //sockfd：对应操作的文件描述符。表示从该文件描述符索引的文件当中读取数据。
                //src_addr：对端网络相关的属性信息，包括协议家族、IP地址、端口号等。
                //addrlen：调用时传入期望读取的src_addr结构体的长度，返回时代表实际读取到的src_addr结构体的长度，这是一个输入输出型参数。
                //0表示阻塞读取

                //1.【收.读】：将客户端的发送的消息读到缓冲区中，
                //将用户端的属性信息设置进struct sockaddr结构体中
                struct sockaddr_in peer;
                socklen_t len = sizeof(peer);
                int n = recvfrom(sock_,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);
                if(n>0) buffer[n] = '\0';
                else continue;

                //提取client信息---debug
                //将整数IP转换成字符串IP的函数叫做inet_ntoa
                std::string clientip = inet_ntoa(peer.sin_addr);
                uint16_t clientport = ntohs((peer.sin_port));
                //打印获取到的客户端ip地址和端口号，以及内容
                std::cout<<clientip<<"-"<<clientport<<"# "<<buffer<<std::endl;

                //构建一个用户，并检查
                std::string name=clientip;
                name +="-";
                name+=std::to_string(clientport);

                //如果用户不存在就存储到哈希表中，如果存在则什么都不做
                //添加用户的时候，很多用户会访问哈希表，所以存在安全问题。
                addUser(name,peer);
                std::string massage = name +">> " +buffer;
                //环形队列用来存储，客户端发送的消息buffer
                rq.push(massage);

                //buffer是服务器收到的客户端消息，交给service_函数做业务处理
                //回调service函数，返回类型是个字符串,参数也是字符串
                //std::string massage = service_(buffer);

                //群发消息：环形队列-生产者消费者模型


                //【发】:把缓冲区中的有效字符发给客户端,不带'\0',strlen无需+1
                //ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                //      const struct sockaddr *dest_addr, socklen_t addrlen);
                //服务器将客户端的需求执行完后，返回给客户端
                //sendto(sock_,massage.c_str(),massage.size(),0,(struct sockaddr*)&peer,sizeof(peer));
            }
        }
        //群发消息版本
        void Broadcast()
        {
            while(true)
            {
                std::string sendstring;
                rq.pop(&sendstring);

                std::vector<struct sockaddr_in> v;//存储客户端的属性信息
                {
                    LockGuard lockguard(&lock);
                    for(auto user : onlineuser)
                    {
                        v.push_back(user.second);
                    }
                }
                //遍历存储客户端属性的vector
                for(auto user:v)
                {
                    //群发给客户端
                    sendto(sock_,sendstring.c_str(),sendstring.size(),0,(struct sockaddr*)&(user),sizeof(user));
                    std::cout<<"send done..."<<sendstring<<std::endl;
                }
            }
            /*std::string sendstring;
            rq.pop(&sendstring);//获取环形队列的数据

            LockGuard lockguard(&lock);
            //服务器拿到某个客户端的消息之后，就群发给用户
            for(auto user:onlineuser)
            {
                std::cout<<"Broadcast message to"<<user.first<<sendstring<<std::endl;
                sendto(sock_,sendstring.c_str(),sendstring.size(),0,(struct sockaddr*)&(user.second),sizeof(user.second));
            }*/
        }
        ~UdpServer(){
            pthread_mutex_destroy(&lock);
            c->join();
            p->join();

            delete c;
            delete p;
        }
    private:
        int sock_;//套接字文件描述符
        uint16_t port_;//服务器端口号
        //func_t service_;//我们的网络服务器刚刚解决的是网络IO收发的问题，而服务器要解决业务问题
        //std::string ip_;//服务器ip地址

        //在线用户列表
        std::unordered_map<std::string,struct sockaddr_in> onlineuser;//用于存储访问当前服务器的在线用户
        pthread_mutex_t lock;
        RingQueue<std::string> rq;
        //两个线程:服务器端线程化,生产者p执行recv函数，获取数据(recvfrom)，生产数据(push)
        //消费者c执行broadcast函数，(pop)拿到环形队列的数据,(sendto)处理数据发送到客户端
        Thread *c;
        Thread *p;
    };
}
